package com.qijian.aop;

import com.qijian.common.annotation.PlugAnn;
import com.qijian.common.annotation.PlugParam;
import com.qijian.framework.manager.AsyncManager;
import com.qijian.plug.AbstractPlug;
import lombok.SneakyThrows;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ResolvableType;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 绩效检查Aop
 */
@Aspect
@Component
public class PlugAop {

    private static final ExpressionParser parser = new SpelExpressionParser();
    private final String POINT_CUT = "@annotation(com.qijian.common.annotation.PlugAnn)";

    private List<AbstractPlug> EXAMINE;

    @Autowired
    public void setEXAMINE(List<AbstractPlug> EXAMINE) {
        this.EXAMINE = EXAMINE;
    }

    @AfterReturning(value = POINT_CUT, returning = "rv")
    public void serviceImplAfter(JoinPoint joinPoint, Object rv) throws NoSuchMethodException {
        Object clazz = joinPoint.getTarget();
        Signature signature = joinPoint.getSignature();
        //获取触发的方法
        Method realMethod = clazz.getClass().getDeclaredMethod(signature.getName(), ((MethodSignature) signature).getParameterTypes());
        PlugAnn plugAnn = realMethod.getAnnotation(PlugAnn.class);
        Object[] SPELParams = new Object[plugAnn.params().length];
        for (int i = 0; i < plugAnn.params().length; i++) {
            SPELParams[i] = parser.parseExpression(plugAnn.params()[i]).getValue();
        }
        AsyncManager.me().execute(task(clazz, joinPoint.getArgs(), realMethod, plugAnn, SPELParams, rv));
//        process(clazz, joinPoint.getArgs(), realMethod, plugAnn, SPELParams, rv);
    }

    /**
     * 异步方法，暂时不使用
     */
    private TimerTask task(Object clazz, Object[] args, Method realMethod, PlugAnn plugAnn, Object[] SPELParams, Object rv) {
        return new TimerTask() {
            @SneakyThrows
            @Override
            public void run() {
                process(clazz, args, realMethod, plugAnn, SPELParams, rv);
            }
        };
    }

    private void process(Object clazz, Object[] args, Method realMethod, PlugAnn plugAnn, Object[] SPELParams, Object rv) {
        if (plugAnn.isExecPlug() && rv instanceof Boolean) {
            if (!(Boolean) rv)
                return;
        }
        //待执行的外挂方法
        List<AbstractPlug> ex = new ArrayList<>();
        //如果本身就是外挂器，就执行自己的规则
        if (clazz instanceof AbstractPlug)
            ex.add((AbstractPlug) clazz);
        else {
            Class<?>[] annGroupClass = plugAnn.groupClass();
            if (annGroupClass.length == 0) {
                //自动匹配spring容器中的外挂器
                //默认根据每个ServiceImpl里继承mybatis-plus的ServiceImpl第二个范型去自动匹配外挂的bean
                //ServiceImpl会有2个范型
//                Type[] types = ((ParameterizedType) getOriginalClass(clazz).getClass().getGenericSuperclass()).getActualTypeArguments();
                ResolvableType[] types = getGenericType(clazz);
                if (types.length == 2) {
                    annGroupClass = new Class[]{types[1].resolve()};
                } else
                    throw new UnsupportedOperationException("无法自动匹配外挂器,请指定@ExamineAnn中groupClass");
            }

            for (AbstractPlug e : EXAMINE) {
                Class<?> c = getGenericType(e)[0].resolve();
//                Type t = ((ParameterizedType) getOriginalClass(e).getClass().getGenericSuperclass()).getActualTypeArguments()[0];
                if (c != null) {
                    for (Class<?> ac : annGroupClass) {
                        if (c.isAssignableFrom(ac)) {
                            ex.add(e);
                        }
                    }
                }
            }
        }
        //匹配到外挂器
        if (ex.size() > 0) {
            Map<Integer, List<Object>> tempArgMap = new HashMap<>();
            List<Object> argList = new ArrayList<>();
            Annotation[][] parameterAnnotations = realMethod.getParameterAnnotations();
            //检查参数的@ExamineParam注解
            for (int i = 0; i < parameterAnnotations.length; i++) {
                Annotation[] annotations = parameterAnnotations[i];
                for (Annotation annotation : annotations) {
                    if (PlugParam.class.isAssignableFrom(annotation.annotationType())) {
                        Integer order = ((PlugParam) annotation).value();
                        if (!tempArgMap.containsKey(order)) {
                            tempArgMap.put(order, new ArrayList<>());
                        }
                        tempArgMap.get(order).add(args[i]);
                        break;
                    }
                }
            }
            if (tempArgMap.size() > 0)
                tempArgMap.keySet().stream().sorted().forEach(key -> argList.addAll(tempArgMap.get(key)));
            if (SPELParams.length > 0) argList.addAll(Arrays.asList(SPELParams));
            if (plugAnn.value() && rv != null) argList.add(rv);
            /*
             * 参数的顺序为三部分
             *  1:@PlugParam标注的部分
             *  2:@PlugAnn里的params
             *  3:@PlugAnn标记需要返回值后，会在最后添加返回值
             */
            //遍历执行外挂
            for (AbstractPlug e : ex) {
                e.doSomething(argList);
            }
        }
    }

    private ResolvableType[] getGenericType(Object obj) {
        Assert.notNull(obj, "Plug Aop getGenericType():  obj must not be null");
        Class<?> clazz;
        if (AopUtils.isAopProxy(obj)) {
            clazz = AopUtils.getTargetClass(obj);
        } else {
            clazz = obj.getClass();
        }
        ResolvableType resolvableType = ResolvableType.forClass(clazz).getSuperType();
        if (ArrayUtils.isEmpty(resolvableType.getGenerics()))
            throw new IllegalArgumentException("无法获取到匹配范型!");
        return resolvableType.getGenerics();
    }
}
